unit Setup.RedirFunc;

{
  Inno Setup
  Copyright (C) 1997-2025 Jordan Russell
  Portions by Martijn Laan
  For conditions of distribution and use, see LICENSE.TXT.

  Functions for dealing with WOW64 file system redirection.
  Used only by the Setup and SetupLdr projects.

  The *Redir functions are counterparts to common functions that offer
  built-in support for disabling FS redirection.
}

interface

uses
  Windows, SysUtils, Shared.FileClass, Shared.VerInfoFunc;

type
  TPreviousFsRedirectionState = record
    DidDisable: Boolean;
    OldValue: Pointer;
  end;

function AreFsRedirectionFunctionsAvailable: Boolean;
function DisableFsRedirectionIf(const Disable: Boolean;
  var PreviousState: TPreviousFsRedirectionState): Boolean;
procedure RestoreFsRedirection(const PreviousState: TPreviousFsRedirectionState);

function CreateFileRedir(const DisableFsRedir: Boolean; const FileName: String;
  const DesiredAccess, ShareMode: DWORD; const SecurityAttributes: PSecurityAttributes;
  const CreationDisposition, FlagsAndAttributes: DWORD; TemplateFile: THandle): THandle;
function CreateDirectoryRedir(const DisableFsRedir: Boolean; const Filename: String;
  const SecurityAttributes: PSecurityAttributes = nil): BOOL;
function CreateProcessRedir(const DisableFsRedir: Boolean;
  const lpApplicationName: PChar; const lpCommandLine: PChar;
  const lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
  const bInheritHandles: BOOL; const dwCreationFlags: DWORD;
  const lpEnvironment: Pointer; const lpCurrentDirectory: PChar;
  const lpStartupInfo: TStartupInfo;
  var lpProcessInformation: TProcessInformation): BOOL;
function CopyFileRedir(const DisableFsRedir: Boolean;
  const ExistingFilename, NewFilename: String; const FailIfExists: BOOL): BOOL;
function DeleteFileRedir(const DisableFsRedir: Boolean; const Filename: String): BOOL;
function DirExistsRedir(const DisableFsRedir: Boolean; const Filename: String): Boolean;
function FileOrDirExistsRedir(const DisableFsRedir: Boolean; const Filename: String): Boolean;
function FindFirstFileRedir(const DisableFsRedir: Boolean; const Filename: String;
  var FindData: TWin32FindData): THandle;
function GetFileAttributesRedir(const DisableFsRedir: Boolean; const Filename: String): DWORD;
function GetShortNameRedir(const DisableFsRedir: Boolean; const Filename: String): String;
function GetVersionNumbersRedir(const DisableFsRedir: Boolean; const Filename: String;
  var VersionNumbers: TFileVersionNumbers): Boolean;
function IsDirectoryAndNotReparsePointRedir(const DisableFsRedir: Boolean;
  const Name: String): Boolean;
function MoveFileRedir(const DisableFsRedir: Boolean;
  const ExistingFilename, NewFilename: String): BOOL;
function MoveFileExRedir(const DisableFsRedir: Boolean;
  const ExistingFilename, NewFilename: String; const Flags: DWORD): BOOL;
function NewFileExistsRedir(const DisableFsRedir: Boolean; const Filename: String): Boolean;
function RemoveDirectoryRedir(const DisableFsRedir: Boolean; const Filename: String): BOOL;
function SetFileAttributesRedir(const DisableFsRedir: Boolean; const Filename: String;
  const Attrib: DWORD): BOOL;
function SetNTFSCompressionRedir(const DisableFsRedir: Boolean; const FileOrDir: String; Compress: Boolean): Boolean;

type
  TFileRedir = class(TFile)
  private
    FDisableFsRedir: Boolean;
  protected
    function CreateHandle(const AFilename: String;
      ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
      ASharing: TFileSharing): THandle; override;
  public
    constructor Create(const DisableFsRedir: Boolean; const AFilename: String;
      ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
      ASharing: TFileSharing);
  end;

  TTextFileReaderRedir = class(TTextFileReader)
  private
    FDisableFsRedir: Boolean;
  protected
    function CreateHandle(const AFilename: String;
      ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
      ASharing: TFileSharing): THandle; override;
  public
    constructor Create(const DisableFsRedir: Boolean; const AFilename: String;
      ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
      ASharing: TFileSharing);
  end;

  TTextFileWriterRedir = class(TTextFileWriter)
  private
    FDisableFsRedir: Boolean;
  protected
    function CreateHandle(const AFilename: String;
      ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
      ASharing: TFileSharing): THandle; override;
  public
    constructor Create(const DisableFsRedir: Boolean; const AFilename: String;
      ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
      ASharing: TFileSharing);
  end;

implementation

uses
  Shared.CommonFunc, PathFunc;

var
  Wow64DisableWow64FsRedirectionFunc: function(var OldValue: Pointer): BOOL; stdcall;
  Wow64RevertWow64FsRedirectionFunc: function(OldValue: Pointer): BOOL; stdcall;
  FsRedirectionFunctionsAvailable: Boolean;

function AreFsRedirectionFunctionsAvailable: Boolean;
begin
  Result := FsRedirectionFunctionsAvailable;
end;

function DisableFsRedirectionIf(const Disable: Boolean;
  var PreviousState: TPreviousFsRedirectionState): Boolean;
{ If Disable is False, the function does not change the redirection state and
  always returns True.
  If Disable is True, the function attempts to disable WOW64 file system
  redirection, so that c:\windows\system32 goes to the 64-bit System directory
  instead of the 32-bit one.
  Returns True if successful, False if not. For extended error information when
  False is returned, call GetLastError. }
begin
  PreviousState.DidDisable := False;
  if not Disable then
    Result := True
  else begin
    if FsRedirectionFunctionsAvailable then begin
      { Note: Disassembling Wow64DisableWow64FsRedirection and the Rtl function
        it calls, it doesn't appear as if it can ever actually fail on 64-bit
        Windows. But it always fails on the 32-bit version of Windows Server
        2003 SP1 (with error code 1 - ERROR_INVALID_FUNCTION). }
      Result := Wow64DisableWow64FsRedirectionFunc(PreviousState.OldValue);
      if Result then
        PreviousState.DidDisable := True;
    end
    else begin
      { Should never happen }
      SetLastError(ERROR_INVALID_FUNCTION);
      Result := False;
    end;
  end;
end;

procedure RestoreFsRedirection(const PreviousState: TPreviousFsRedirectionState);
{ Restores the previous WOW64 file system redirection state after a call to
  DisableFsRedirectionIf. There is no indication of failure (which is
  extremely unlikely). }
begin
  if PreviousState.DidDisable then
    Wow64RevertWow64FsRedirectionFunc(PreviousState.OldValue);
end;

{ *Redir functions }

function CreateFileRedir(const DisableFsRedir: Boolean; const FileName: String;
  const DesiredAccess, ShareMode: DWORD; const SecurityAttributes: PSecurityAttributes;
  const CreationDisposition, FlagsAndAttributes: DWORD; TemplateFile: THandle): THandle;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := INVALID_HANDLE_VALUE;
    Exit;
  end;
  try
    Result := CreateFile(PChar(Filename), DesiredAccess, ShareMode, SecurityAttributes,
      CreationDisposition, FlagsAndAttributes, TemplateFile);
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

function CreateDirectoryRedir(const DisableFsRedir: Boolean; const Filename: String;
  const SecurityAttributes: PSecurityAttributes): BOOL;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := False;
    Exit;
  end;
  try
    Result := CreateDirectory(PChar(Filename), SecurityAttributes);
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

function CreateProcessRedir(const DisableFsRedir: Boolean;
  const lpApplicationName: PChar; const lpCommandLine: PChar;
  const lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
  const bInheritHandles: BOOL; const dwCreationFlags: DWORD;
  const lpEnvironment: Pointer; const lpCurrentDirectory: PChar;
  const lpStartupInfo: TStartupInfo;
  var lpProcessInformation: TProcessInformation): BOOL;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := False;
    Exit;
  end;
  try
    Result := CreateProcess(lpApplicationName, lpCommandLine,
      lpProcessAttributes, lpThreadAttributes,
      bInheritHandles, dwCreationFlags, lpEnvironment,
      lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

function CopyFileRedir(const DisableFsRedir: Boolean;
  const ExistingFilename, NewFilename: String; const FailIfExists: BOOL): BOOL;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := False;
    Exit;
  end;
  try
    Result := CopyFile(PChar(ExistingFilename), PChar(NewFilename), FailIfExists);
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

function DeleteFileRedir(const DisableFsRedir: Boolean; const Filename: String): BOOL;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := False;
    Exit;
  end;
  try
    Result := Windows.DeleteFile(PChar(Filename));
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

function DirExistsRedir(const DisableFsRedir: Boolean; const Filename: String): Boolean;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := False;
    Exit;
  end;
  try
    Result := DirExists(Filename);
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

function FileOrDirExistsRedir(const DisableFsRedir: Boolean; const Filename: String): Boolean;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := False;
    Exit;
  end;
  try
    Result := FileOrDirExists(Filename);
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

function FindFirstFileRedir(const DisableFsRedir: Boolean; const Filename: String;
  var FindData: TWin32FindData): THandle;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := INVALID_HANDLE_VALUE;
    Exit;
  end;
  try
    Result := FindFirstFile(PChar(Filename), FindData);
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

function GetFileAttributesRedir(const DisableFsRedir: Boolean; const Filename: String): DWORD;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := INVALID_FILE_ATTRIBUTES;
    Exit;
  end;
  try
    Result := GetFileAttributes(PChar(Filename));
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

function GetShortNameRedir(const DisableFsRedir: Boolean; const Filename: String): String;
var
  PrevState: TPreviousFsRedirectionState;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := Filename;
    Exit;
  end;
  try
    Result := GetShortName(Filename);
  finally
    RestoreFsRedirection(PrevState);
  end;
end;

function GetVersionNumbersRedir(const DisableFsRedir: Boolean; const Filename: String;
  var VersionNumbers: TFileVersionNumbers): Boolean;
var
  PrevState: TPreviousFsRedirectionState;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := False;
    Exit;
  end;
  try
    Result := GetVersionNumbers(Filename, VersionNumbers);
  finally
    RestoreFsRedirection(PrevState);
  end;
end;

function IsDirectoryAndNotReparsePointRedir(const DisableFsRedir: Boolean;
  const Name: String): Boolean;
var
  PrevState: TPreviousFsRedirectionState;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := False;
    Exit;
  end;
  try
    Result := IsDirectoryAndNotReparsePoint(Name);
  finally
    RestoreFsRedirection(PrevState);
  end;
end;

function MoveFileRedir(const DisableFsRedir: Boolean;
  const ExistingFilename, NewFilename: String): BOOL;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := False;
    Exit;
  end;
  try
    Result := MoveFile(PChar(ExistingFilename), PChar(NewFilename));
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

function MoveFileExRedir(const DisableFsRedir: Boolean;
  const ExistingFilename, NewFilename: String; const Flags: DWORD): BOOL;
var
  NewFilenameP: PChar;
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if (NewFilename = '') and (Flags and MOVEFILE_DELAY_UNTIL_REBOOT <> 0) then
    NewFilenameP := nil
  else
    NewFilenameP := PChar(NewFilename);
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := False;
    Exit;
  end;
  try
    Result := MoveFileEx(PChar(ExistingFilename), NewFilenameP, Flags);
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

function NewFileExistsRedir(const DisableFsRedir: Boolean; const Filename: String): Boolean;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := False;
    Exit;
  end;
  try
    Result := NewFileExists(Filename);
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

function RemoveDirectoryRedir(const DisableFsRedir: Boolean; const Filename: String): BOOL;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := False;
    Exit;
  end;
  try
    Result := RemoveDirectory(PChar(Filename));
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

function SetFileAttributesRedir(const DisableFsRedir: Boolean; const Filename: String;
  const Attrib: DWORD): BOOL;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := False;
    Exit;
  end;
  try
    Result := SetFileAttributes(PChar(Filename), Attrib);
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

function SetNTFSCompressionRedir(const DisableFsRedir: Boolean; const FileOrDir: String; Compress: Boolean): Boolean;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
    Result := False;
    Exit;
  end;
  try
    Result := SetNTFSCompression(FileOrDir, Compress);
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

{ TFileRedir }

constructor TFileRedir.Create(const DisableFsRedir: Boolean; const AFilename: String;
  ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  ASharing: TFileSharing);
begin
  FDisableFsRedir := DisableFsRedir; 
  inherited Create(AFilename, ACreateDisposition, AAccess, ASharing);
end;

function TFileRedir.CreateHandle(const AFilename: String;
  ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  ASharing: TFileSharing): THandle;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(FDisableFsRedir, PrevState) then begin
    Result := INVALID_HANDLE_VALUE;
    Exit;
  end;
  try
    Result := inherited CreateHandle(AFilename, ACreateDisposition, AAccess,
      ASharing);
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

{ TTextFileReaderRedir }

constructor TTextFileReaderRedir.Create(const DisableFsRedir: Boolean; const AFilename: String;
  ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  ASharing: TFileSharing);
begin
  FDisableFsRedir := DisableFsRedir; 
  inherited Create(AFilename, ACreateDisposition, AAccess, ASharing);
end;

function TTextFileReaderRedir.CreateHandle(const AFilename: String;
  ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  ASharing: TFileSharing): THandle;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(FDisableFsRedir, PrevState) then begin
    Result := INVALID_HANDLE_VALUE;
    Exit;
  end;
  try
    Result := inherited CreateHandle(AFilename, ACreateDisposition, AAccess,
      ASharing);
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

{ TTextFileWriterRedir }

constructor TTextFileWriterRedir.Create(const DisableFsRedir: Boolean; const AFilename: String;
  ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  ASharing: TFileSharing);
begin
  FDisableFsRedir := DisableFsRedir; 
  inherited Create(AFilename, ACreateDisposition, AAccess, ASharing);
end;

function TTextFileWriterRedir.CreateHandle(const AFilename: String;
  ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  ASharing: TFileSharing): THandle;
var
  PrevState: TPreviousFsRedirectionState;
  ErrorCode: DWORD;
begin
  if not DisableFsRedirectionIf(FDisableFsRedir, PrevState) then begin
    Result := INVALID_HANDLE_VALUE;
    Exit;
  end;
  try
    Result := inherited CreateHandle(AFilename, ACreateDisposition, AAccess,
      ASharing);
    ErrorCode := GetLastError;
  finally
    RestoreFsRedirection(PrevState);
  end;
  SetLastError(ErrorCode);
end;

initialization
  Wow64DisableWow64FsRedirectionFunc := GetProcAddress(GetModuleHandle(kernel32),
    'Wow64DisableWow64FsRedirection');
  Wow64RevertWow64FsRedirectionFunc := GetProcAddress(GetModuleHandle(kernel32),
    'Wow64RevertWow64FsRedirection');
  FsRedirectionFunctionsAvailable := Assigned(Wow64DisableWow64FsRedirectionFunc) and
    Assigned(Wow64RevertWow64FsRedirectionFunc);

  { For GetVersionNumbersRedir: Pre-load shell32.dll since GetFileVersionInfo
    and GetFileVersionInfoSize will try to load it when reading version info
    on 16-bit files. We can't allow the DLL be loaded for the first time while
    FS redirection is disabled. }
  SafeLoadLibrary(AddBackslash(GetSystemDir) + 'shell32.dll', SEM_NOOPENFILEERRORBOX);

  { FormatMessage might be called with FS redirection disabled, so ensure
    that all the DLLs FormatMessage searches in for messages (e.g. netmsg.dll,
    ws03res.dll) are pre-loaded by calling it now with a randomly-chosen
    message ID -- one that won't result in a match and cause the function to
    return early.
    (Note: Presently, FormatMessage loads the DLLs as "data files" so it
    actually may not matter whether it gets 32- or 64-bit versions. But let's
    be on the safe side.) }
  Win32ErrorString($4C783AFB);
end.
